
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       gp_rtc.c
  * @author     baiyang
  * @date       2022-1-25
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include <rtc/gp_rtc.h>
#include <parameter/param.h>
#include <gcs_mavlink/gcs.h>
#include <common/time/gp_time.h>
/*-----------------------------------macro------------------------------------*/

/*----------------------------------typedef-----------------------------------*/

/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/
static param_t var_info[] = {
    PARAM_DEFINE_INT8(RTC_TYPES, 1),
    PARAM_DEFINE_INT16(RTC_TZ_MIN, 0),
};
PARAM_GROUP_DEFINE(RTC, var_info);

static gp_rtc _rtc;
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
void rtc_ctor()
{
    _rtc.rtc_source_type = SOURCE_NONE;

    rt_mutex_init(&_rtc._mutex, "gp_rtc", RT_IPC_FLAG_FIFO);

    param_link_variable(PARAM_ID(RTC, RTC_TYPES), &_rtc.allowed_types);
    param_link_variable(PARAM_ID(RTC, RTC_TZ_MIN), &_rtc.tz_min);
}

void rtc_set_utc_usec(uint64_t time_utc_usec, enum source_type type)
{
    const uint64_t oldest_acceptable_date = 1546300800000; // 2019-01-01 0:00

    if (type >= _rtc.rtc_source_type) {
        // e.g. system-time message when we've been set by the GPS
        return;
    }

    // check it's from an allowed sources:
    if (!(_rtc.allowed_types & (1<<type))) {
        return;
    }

    // don't allow old times
    if (time_utc_usec < oldest_acceptable_date) {
        return;
    }

    const uint64_t now = time_micros64();
    const int64_t tmp = (int64_t)(time_utc_usec) - (int64_t)(now);
    if (tmp < _rtc.rtc_shift) {
        // can't allow time to go backwards, ever
        return;
    }
    rt_mutex_take(&_rtc._mutex, RT_WAITING_FOREVER);

    _rtc.rtc_shift = tmp;

    // update hardware clock:
    if (type != SOURCE_HW) {
        time_set_hw_rtc(time_utc_usec);
    }

    _rtc.rtc_source_type = type;

    // update signing timestamp
    gcsmav_update_signing_timestamp(time_utc_usec);

    rt_mutex_release(&_rtc._mutex);
}

bool rtc_get_utc_usec(uint64_t *usec)
{
    if (_rtc.rtc_source_type == SOURCE_NONE) {
        return false;
    }
    *usec = time_micros64() + _rtc.rtc_shift;
    return true;
}

bool rtc_get_system_clock_utc(uint8_t *hour, uint8_t *min, uint8_t *sec, uint16_t *ms)
{
     // get time of day in ms
    uint64_t time_ms = 0;
    if (!rtc_get_utc_usec(&time_ms)) {
        return false;
    }
    time_ms /= 1000U;

    // separate time into ms, sec, min, hour and days but all expressed in milliseconds
    *ms = time_ms % 1000;
    uint32_t sec_ms = (time_ms % (60 * 1000)) - *ms;
    uint32_t min_ms = (time_ms % (60 * 60 * 1000)) - sec_ms - *ms;
    uint32_t hour_ms = (time_ms % (24 * 60 * 60 * 1000)) - min_ms - sec_ms - *ms;

    // convert times as milliseconds into appropriate units
    *sec = sec_ms / 1000;
    *min = min_ms / (60 * 1000);
    *hour = hour_ms / (60 * 60 * 1000);

    return true;
}

bool rtc_get_local_time(uint8_t *hour, uint8_t *min, uint8_t *sec, uint16_t *ms)
{
     // get local time of day in ms
    uint64_t time_ms = 0;
    uint64_t ms_local = 0;
    if (!rtc_get_utc_usec(&time_ms)) {
        return false;
    }
    time_ms /= 1000U;
    ms_local = time_ms + (_rtc.tz_min * 60000);

    // separate time into ms, sec, min, hour and days but all expressed in milliseconds
    *ms = ms_local % 1000;
    uint32_t sec_ms = (ms_local % (60 * 1000)) - *ms;
    uint32_t min_ms = (ms_local % (60 * 60 * 1000)) - sec_ms - *ms;
    uint32_t hour_ms = (ms_local % (24 * 60 * 60 * 1000)) - min_ms - sec_ms - *ms;

    // convert times as milliseconds into appropriate units
    *sec = sec_ms / 1000;
    *min = min_ms / (60 * 1000);
    *hour = hour_ms / (60 * 60 * 1000);

    return true;
}

// get milliseconds from now to a target time of day expressed as
// hour, min, sec, ms.  Match starts from first value that is not
// -1. I.e. specifying hour=-1, minutes=10 will ignore the hour and
// return time until 10 minutes past 12am (utc) NOTE: if this time has
// just past then you can expect a return value of roughly 86340000 -
// the number of milliseconds in a day.
uint32_t rtc_get_time_utc(int32_t hour, int32_t min, int32_t sec, int32_t ms)
{
    // determine highest value specified (0=none, 1=ms, 2=sec, 3=min, 4=hour)
    int8_t largest_element = 0;
    if (hour != -1) {
        largest_element = 4;
    } else if (min != -1) {
        largest_element = 3;
    } else if (sec != -1) {
        largest_element = 2;
    } else if (ms != -1) {
        largest_element = 1;
    } else {
        // exit immediately if no time specified
        return 0;
    }

    // get start_time_ms as h, m, s, ms
    uint8_t curr_hour, curr_min, curr_sec;
    uint16_t curr_ms;
    if (!rtc_get_system_clock_utc(&curr_hour, &curr_min, &curr_sec, &curr_ms)) {
        return 0;
    }
    int32_t total_delay_ms = 0;

    // calculate ms to target
    if (largest_element >= 1) {
        total_delay_ms += ms - curr_ms;
    }
    if (largest_element == 1 && total_delay_ms < 0) {
        return (uint32_t)(total_delay_ms += 1000);
    }

    // calculate sec to target
    if (largest_element >= 2) {
        total_delay_ms += (sec - curr_sec)*1000;
    }
    if (largest_element == 2 && total_delay_ms < 0) {
        return (uint32_t)(total_delay_ms += (60*1000));
    }

    // calculate min to target
    if (largest_element >= 3) {
        total_delay_ms += (min - curr_min)*60*1000;
    }
    if (largest_element == 3 && total_delay_ms < 0) {
        return (uint32_t)(total_delay_ms += (60*60*1000));
    }

    // calculate hours to target
    if (largest_element >= 4) {
        total_delay_ms += (hour - curr_hour)*60*60*1000;
    }
    if (largest_element == 4 && total_delay_ms < 0) {
        return (uint32_t)(total_delay_ms += (24*60*60*1000));
    }

    // total delay in milliseconds
    return (uint32_t)(total_delay_ms);
}

/*
  mktime replacement from Samba
 */
time_t rtc_mktime(const struct tm *t)
{
    time_t epoch = 0;
    int n;
    int mon [] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, y, m, i;
    const unsigned MINUTE = 60;
    const unsigned HOUR = 60*MINUTE;
    const unsigned DAY = 24*HOUR;
    const unsigned YEAR = 365*DAY;

    if (t->tm_year < 70) {
        return (time_t)-1;
    }

    n = t->tm_year + 1900 - 1;
    epoch = (t->tm_year - 70) * YEAR +
            ((n / 4 - n / 100 + n / 400) - (1969 / 4 - 1969 / 100 + 1969 / 400)) * DAY;

    y = t->tm_year + 1900;
    m = 0;

    for (i = 0; i < t->tm_mon; i++) {
        epoch += mon [m] * DAY;
        if (m == 1 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) {
            epoch += DAY;
        }

        if (++m > 11) {
            m = 0;
            y++;
        }
    }

    epoch += (t->tm_mday - 1) * DAY;
    epoch += t->tm_hour * HOUR + t->tm_min * MINUTE + t->tm_sec;

    return epoch;
}

/*------------------------------------test------------------------------------*/


